package es.gestores;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import es.componentes.Componente;
import es.entidades.Entidad;
/**
 * Gestiona los componentes.
 * @author Miguel Vicente
 */
public class GestorComponentes {
	
	private HashMap<String, ArrayList<Componente>> componentes;
	private ArrayList<GestorComponentesListener> listeners;
	
	/**
	 * Constructor. Crea un nuevo GestorComponentes
	 */
	public GestorComponentes() {
		this.componentes = new HashMap<String, ArrayList<Componente>>();
		this.listeners = new ArrayList<GestorComponentesListener>();
	}
	/**
	 * Devuelve un ArrayList con todos los componentes del tipo especificado.
	 * @param tipo
	 * @return ArrayList
	 */
	public ArrayList<Componente> getComponentesTipo(String tipo) {
		ArrayList<Componente> componentesMismoTipo = componentes.get(tipo);
		if (componentesMismoTipo == null) {
			componentesMismoTipo = new ArrayList<Componente>();
			componentes.put(tipo, componentesMismoTipo);
		}
		return componentesMismoTipo;
	}
	/**
	 * Busca si hay un componente del tipo especificado asociado a la entidad especificada.<br>
	 * Si el componente esta en el GestorComponentes, lo devuelve, si no, devuelve null
	 * @param entidad Entidad especificada
	 * @param tipo Tipo de componente especificado
	 * @return Componente o null
	 */
	public Componente getComponenteEntidadTipo(Entidad entidad, String tipo) {
		ArrayList<Componente> componentesMismoTipo;
		if (entidad == null) {
			return null;
		}
		componentesMismoTipo = getComponentesTipo(tipo);
		for (int i = 0; i < componentesMismoTipo.size(); i++) {
			if (componentesMismoTipo.get(i).getEntidad().getId() == entidad.getId()) {
				return componentesMismoTipo.get(i);
			}
		}
		return null;
	}
	/**
	 * Agrega un componente al gestor de componentes.<br>
	 * Notifica a todos los GestorComponentesListener que el componente ha sido agregado.
	 * Si el componente no esta asociado a ninguna entidad, no se agregara.
	 * @param componente Componente a agregar
	 */
	public void agregarComponente(Componente componente) {
		ArrayList<Componente> componentesMismoTipo;
		boolean entradaEliminada = false;
		
		if (componente == null) {
			return;
		}
		if (componente.getEntidad() == null) {
			return;
		}
		componentesMismoTipo = getComponentesTipo(componente.getTipo());
		// Si la entidad la que hace referencia este componente ya tiene un componente del mismo tipo
		// eliminamos el componente antiguo y agregamos el nuevo
		for (int i = 0; i < componentesMismoTipo.size(); i++) {
			if (componentesMismoTipo.get(i).getEntidad().getId() == componente.getEntidad().getId()) {
				entradaEliminada = eliminarComponente(componentesMismoTipo.get(i));
				if (entradaEliminada) {
					ArrayList<Componente> componentesAux = getComponentesTipo(componente.getTipo());
					componentesAux.add(componente);
					componentes.put(componente.getTipo(), componentesAux);
				}
				break;
			}
		}
		if (!entradaEliminada) {
			componentesMismoTipo.add(componente);
		}
		notificarComponenteAgregado(componente);
	}
	/**
	 * Elimina del gestor todos los componentes asociados a una Entidad.<br>
	 * @param entidad Entidad
	 */
	public void eliminarComponentes(Entidad entidad) {
		ArrayList<Componente> componentesEntidad = new ArrayList<Componente>();
		if (entidad != null) {
			for (Iterator<Map.Entry<String, ArrayList<Componente>>> it = componentes.entrySet().iterator(); it.hasNext();)	{
				Map.Entry<String, ArrayList<Componente>> entrada = it.next();
				ArrayList<Componente> comps = entrada.getValue();
				for (int i = 0; i < comps.size(); i++) {
					if (comps.get(i).getEntidad().getId() == entidad.getId()) {
						componentesEntidad.add(comps.get(i));
					}
				}
			}
			if (componentesEntidad.size() > 0) {
				for (Componente componente : componentesEntidad) {
					eliminarComponente(componente);
				}
			}
		}
	}
	/**
	 * Elimina del gestor la referencia al componente especificado. Notifica a todos los
	 * GestorComponentesListener que el componente ha sido eliminado.
	 * @param componente Componente a eliminar
	 * @return True tras eliminaro no quedan mas componentes de ese tipo en el gestor, false en
	 * caso contrario.
	 */
	public boolean eliminarComponente(Componente componente) {
		ArrayList<Componente> componentesMismoTipo;
		String tipo;
		boolean entradaEliminada = false;
		if (componente != null) {
			componentesMismoTipo = getComponentesTipo(componente.getTipo());
			if (componentesMismoTipo != null) {
				for (int i = 0; i < componentesMismoTipo.size(); i++) {
					if (componentesMismoTipo.get(i).getId() == componente.getId()) {
						notificarComponenteEliminado(componentesMismoTipo.get(i));
						// Si no hay mas componentes con el mismo tipo, se elimina la entrada del mapa
						if (componentesMismoTipo.size() == 1) {
							tipo = componentesMismoTipo.get(i).getTipo();
							componentesMismoTipo.remove(i);
							eliminarEntrada(tipo);
							entradaEliminada = true;
						} else { // Si no, se elimina el componente
							componentesMismoTipo.remove(i);
						}
					}
				}
			}
		}
		return entradaEliminada;
	}
	/**
	 * Elimina del gestor el componente especificado por su ID. Notifica a todos los
	 * GestorComponentesListener que el componente ha sido eliminado.
	 * @param id Identificador unico del componente
	 */
	public void eliminarComponente(int id) {
		String tipo;
		Iterator<Entry<String, ArrayList<Componente>>> it = componentes.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, ArrayList<Componente>> entrada = (Entry<String, ArrayList<Componente>>) it.next();
			ArrayList<Componente> compsEntrada = entrada.getValue();
			for (int i = 0; i < compsEntrada.size(); i++) {
				if (compsEntrada.get(i).getId() == id) {
					// Si no hay mas componentes con el mismo tipo, se elimina la entrada del mapa
					if (compsEntrada.size() == 1) {
						tipo = compsEntrada.get(i).getTipo();
						eliminarEntrada(tipo);
					} else { // Si no, se elimina el componente
						notificarComponenteEliminado(compsEntrada.get(i));
						compsEntrada.remove(i);
					}
				}
			}
		}
	}
	/**
	 * Elimina una entrada del mapa de componentes.
	 * @param tipo Tipo que identifica la entrada a eliminar
	 */
	private void eliminarEntrada(String tipo) {
		for (Iterator<Map.Entry<String, ArrayList<Componente>>> it = componentes.entrySet().iterator(); it.hasNext();) {
			Map.Entry<String, ArrayList<Componente>> entrada2 = it.next();
		    if (entrada2.getKey() == tipo) {
		    	ArrayList<Componente> componentesEntrada = entrada2.getValue();
		    	if (componentesEntrada != null) {
					for (int i = 0; i < componentesEntrada.size(); i++) {
						notificarComponenteEliminado(componentesEntrada.get(i));
						componentesEntrada.remove(i);
					}
				}
		        it.remove();  
		    }
		}
	}
	/**
	 * Elimina todos los componentes del gestor.
	 */
	public void eliminarTodo() {
		Iterator<Entry<String, ArrayList<Componente>>> it = componentes.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, ArrayList<Componente>> e = (Map.Entry<String, ArrayList<Componente>>)it.next();
			ArrayList<Componente> comps = e.getValue();
			for (int i = 0; i < comps.size(); i++) {
				notificarComponenteEliminado(comps.get(i));
				comps.remove(i);
			}
			it.remove();
		}
	}
	/**
	 * Devuelve todos los componentes.
	 * @return {@code HashMap<String, ArrayList<Componente>>}
	 */
	public HashMap<String, ArrayList<Componente>> getComponentes() {
		return componentes;
	}
	/**
	 * Pone en escucha un GestorComponentesListener
	 * @param listener GestorComponentesListener
	 */
	public void addGestorComponentesListener(GestorComponentesListener listener) {
		listeners.add(listener);
	}
	/**
	 * Notifica a todos los GestorComponentesListener que un Componente ha sido agregado al gestor.
	 * @param componente Componente agregado
	 */
	public void notificarComponenteAgregado(Componente componente) {
		for (GestorComponentesListener listener : listeners) {
			listener.componenteAgregado(componente);
		}
	}
	/**
	 * Notifica a todos los GestorComponentesListener que un Componente ha sido eliminado del gestor.
	 * @param componente Componente eliminado
	 */
	public void notificarComponenteEliminado(Componente componente) {
		for (GestorComponentesListener listener : listeners) {
			listener.componenteEliminado(componente);
		}
	}
	
	@Override
	public String toString() {
		String s = "---------------GestorComponentes---------------\n";
		s += "---Componentes por tipo:\n";
		Iterator<Entry<String, ArrayList<Componente>>> it = componentes.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, ArrayList<Componente>> e = (Map.Entry<String, ArrayList<Componente>>)it.next();
			s += "-Tipo: " + e.getKey() + "\n";
			ArrayList<Componente> comps = e.getValue();
			for (int i = 0; i < comps.size(); i++) {
				s += comps.get(i).toString() + "\n";
			}
		}
		s += "---------------------------------------------\n";
		return s;
	}
	
}
